#include "lib.h"
#include "list.h"
#include "hashtable.h"
using namespace Library;

#include "packagemanagement.h"
#include "pkgmgr.h"
#include "pkg.h"
#include "packagerequirements.h"
using namespace PackageManagement;


//----------------------------------------------------------------------------
// Public
//
PackageRequirements::PackageRequirements()
{
	m_requiresTable = new RequiresTable(30);
}

PackageRequirements::~PackageRequirements()
{
	clean();

	delete m_requiresTable;
	m_requiresTable = 0;
}

bool8
PackageRequirements::build(pPackageMgr packageManager)
{
	HashTableIterator<pPackage> *	iterator;
	TableEntry *					entry;
	pPackage						package;
	bool8							success = true;

	// Wipe out any previous package requirements.
	clean();

	iterator = packageManager->iterator();
	if (!iterator)
		return false;

	// Store the packages in our hashtable for later retrieval.
	for (iterator->begin(); iterator->key(); iterator->operator++(0))
	{
		package	= iterator->value();
		entry	= new TableEntry(package);
		if (!entry)
		{
			success = false;
			break;
		}

		m_requiresTable->add(package->getName(), entry);
	}

	if (!success)
		clean();

	delete iterator;

	return success;
}

void
PackageRequirements::clean()
{
	RequiresTableIterator	iterator(m_requiresTable);

	for (iterator.begin(); iterator.key(); iterator++)
		delete iterator.value();
}

ListIterator<PackageRequirement*> *
PackageRequirements::requirementsOf(const String& package)
{
	TableEntry *	entry;

	if (m_requiresTable->get(package, &entry) == false)
		return 0;

	if (entry->requiresList() == 0 &&
		entry->buildRequiresList(m_requiresTable) == false)
		return 0;

	return entry->requiresList()->listIterator();
}


//============================================================================
// PackageRequirements::TableEntry
//============================================================================

//----------------------------------------------------------------------------
// Public
//
PackageRequirements::TableEntry::TableEntry(pPackage package)
{
	m_package		= package;
	m_requiresList	= 0;
}

PackageRequirements::TableEntry::~TableEntry()
{
	if (!m_requiresList)
		return;

	ListIterator<PackageRequirement*> *requiresListIterator = m_requiresList->listIterator(0);

	while (requiresListIterator->hasNext())
	{
		PackageRequirement* req = requiresListIterator->next();

		delete req;
		req = 0;
	}

	delete requiresListIterator;
	requiresListIterator = 0;

	delete m_requiresList;
	m_requiresList = 0;
}

bool8
PackageRequirements::TableEntry::buildRequiresList(pRequiresTable requiresTable)
{
	List<pPackage>						previouslySeen;
	ListIterator<PackageRequirement*> *	requiresListIterator;
	ListIterator<PackageRequirement*> *	containsListIterator;
	TableEntry *						tableEntry;
	PackageRequirement*					packagereq;
	PackageRequirement*					containreq;
	PkgDependency *						depends;
	uint32								num_depends;
	uint32								i;


//	DEBUGMSG(("building requirements list: %s\n", m_package->getName()));

	// If it's already built, there's no reason to rebuild!
	if (m_requiresList)
		return true;

	// Acquire a list for ourselves.
	m_requiresList = new List<PackageRequirement*>;
	if (!m_requiresList)
		return false;

	// Add our package to the list.  This will become the first package in
	// an iterative search for package requirements.
	m_requiresList->add( new PackageRequirement(m_package, 0) );

	// Acquire an iterator for the list.
	requiresListIterator = m_requiresList->listIterator(m_requiresList->size());

	// Now, walk the list...  (Notice we are walking backwards.  This is
	// important so we develop a priority sorted download list.)
	while (requiresListIterator->hasPrevious())
	{
		packagereq = requiresListIterator->previous();

		// Now check to see if we've seen this package before.  If so, then
		// we have a circular requirements (in which case there's no need to
		// proceed in this direction *again*).
		if (previouslySeen.contains(packagereq->getPackage()))
			continue;

		// Otherise, add it to our previously seen list to avoid circular
		// requirements (doesn't matter where).
		previouslySeen.add(packagereq->getPackage());

		// Now iterate through the list of requirements...adding them to the
		// official list.
		depends		= packagereq->getPackage()->getDependencies();
		num_depends	= packagereq->getPackage()->getNumDependency();

		for (i = 0; i < num_depends; i++)
		{
			// A requires list isn't any good if packages are missing.
			if (!requiresTable->get(depends[i].getName(), &tableEntry))
			{
				delete requiresListIterator;
				requiresListIterator = 0;

				delete m_requiresList;
				m_requiresList = 0;

				return false;
			}

			// We don't allow addition or promotion of ourselves!  (see below)
			if (m_package == tableEntry->m_package)
				continue;

			// Check to see if this package is already listed as a requirement.
			// If so, promote the requirement since multiple packages rely
			// on it.
			containsListIterator = m_requiresList->listIterator(m_requiresList->size());

			PackageRequirement* depreq = 0;
			while (containsListIterator->hasPrevious())
			{
				containreq = containsListIterator->previous();

				if (containreq->getPackage() == tableEntry->m_package)
				{
					depreq = containreq;
					break;
				}
			}

			delete containsListIterator;
			containsListIterator = 0;

			if (depreq)
			{
				m_requiresList->remove(depreq);

				//promote higher version
				if (depends[i].getRequiredVersion() > depreq->getVersion())
				{
					delete depreq;
					depreq = 0;
				}
			}
			
			if (!depreq)
			{
				depreq = new PackageRequirement(tableEntry->m_package, depends[i].getRequiredVersion());
			}

			// Notice that packages are added to the beginning of the list.
			// The beginning is higher priority.
			m_requiresList->addIndex(0, depreq);
		}
	}
	
	delete requiresListIterator;
	requiresListIterator = 0;

	return true;
}

List<PackageRequirement*> *
PackageRequirements::TableEntry::requiresList()
{
	return m_requiresList;
}
